package graph

import (
	"time"
	"sync"
	"strconv"
	"path/filepath"
	"code.google.com/p/go-uuid/uuid"
)

type Edge struct {
	Index int
	UUID string
	Object interface{}
	Node string
	Relation string
	TimeStamp string 
}

type Row struct {
	Id string
	Rel string
	Edge Edge
	Deleted bool
}

type Node struct {
	Object interface{}
	*sync.Cond
	Count int
}

type Graph struct {
	Rows []Row
	Nodes map[string]*Node
	Name string
	writes int
	full bool
	capacity int
}

func New(name string, capacity int) *Graph {

	return &Graph{make([]Row, capacity), make(map[string]*Node), name, 0, false, capacity}
}

func (g *Graph) Node(id string) *Node {

	node, ok := g.Nodes[id]

	if !ok {
		node = &Node{nil, sync.NewCond(&sync.Mutex{}), 0}
		g.Nodes[id] = node
	}
	

	return node
}

func (g *Graph) Link(nid, rel string, obj interface{}, uu string) {

	i := g.writes % g.capacity

	if uu == "" {
		uu = uuid.New()
	}

	stamp := strconv.FormatInt(time.Now().Unix(), 10)
	
	g.Rows[i] = Row{nid, rel, Edge{g.Node(nid).NextIndex(), uu, obj, nid, rel, stamp}, false}

	g.Node(nid).Broadcast()

	g.writes++

	if g.writes > g.capacity {
		g.full = true
	}
}

func (g *Graph) SetNode(id string, obj interface{}) {

	g.Node(id).Object = obj
}

func (n *Node) GetString(k string) string {

	return n.Object.(map[string]interface{})[k].(string)
}

func (e *Edge) GetString(k string) string {

	return e.Object.(map[string]interface{})[k].(string)
}

func (n *Node) NextIndex() int {

	n.Count++
	return n.Count
}

func globMatch(pat, str string) bool {

	b, err := filepath.Match(pat, str)

	if err != nil {
		return false
	}

	return b
}

func (g *Graph) EdgesExactly(id, rel string, n int) (results []Edge) {

	since := 0

	results = g.Edges(id, rel, since)

	if len(results) < n {

		node := g.Node(id)

		node.Cond.L.Lock()

		for len(results) < n {
			time.Sleep(500 * time.Millisecond)
			node.Wait()
			results = g.Edges(id, rel, since)
		}

		node.Cond.L.Unlock()
	}

	if len(results) > n {
		results = results[0:n]
	}

	return
}


func (g *Graph) Edges(id, rel string, since int) (feed []Edge) {

	j := 0

	for i := g.writes % g.capacity; j < g.capacity; i = (i + 1) % g.capacity {

		row := g.Rows[i]

		if !row.Deleted && globMatch(id, row.Id) && globMatch(rel, row.Rel) && row.Edge.Index > since {
			feed = append(feed, g.Rows[i].Edge)
		}

		j++
	}

	return
}

func (g *Graph) Relations(id string) (results []string) {

	set := make(map[string]bool)

	j := 0

	for i := g.writes % g.capacity; j < g.capacity; i = (i + 1) % g.capacity {

		row := g.Rows[i]

		if !row.Deleted && globMatch(id, row.Id) {

			if set[row.Rel] {
				results = append(results, row.Rel)
				set[row.Rel] = true
			}
		}

		j++
	}

	return
}

func (g *Graph) Delete(uu string) {

	j := 0

	for i := g.writes % g.capacity; j < g.capacity; i = (i + 1) % g.capacity {

		row := g.Rows[i]

		if row.Edge.UUID == uu {
			row.Deleted = true;
		}

		j++
	}

	return
}

